package simpledb.systemtest;

import simpledb.execution.OpIterator;
import simpledb.execution.SeqScan;
import org.junit.Assert;
import simpledb.common.*;
import simpledb.storage.*;
import simpledb.transaction.TransactionAbortedException;
import simpledb.transaction.TransactionId;

import java.io.File;
import java.io.IOException;
import java.util.*;

/**
 * SystemTestUtil 是 SimpleDB 系统测试工具类。
 * 提供了一些用于创建随机数据文件、验证扫描结果、内存检测等辅助方法。
 */
public class SystemTestUtil {

    /**
     * 单列 INT 类型的元组描述符（TupleDesc）
     */
    public static final TupleDesc SINGLE_INT_DESCRIPTOR = new TupleDesc(new Type[] { Type.INT_TYPE });

    // 随机值的最大范围：2^16
    private static final int      MAX_RAND_VALUE        = 1 << 16;

    /**
     * 创建一个包含指定列数和行数的 HeapFile（堆文件）。
     * 每一列可以指定固定值或随机生成。
     *
     * @param columns 列数
     * @param rows 行数
     * @param columnSpecification 列索引到固定值的映射（null 表示该列随机）
     * @param tuples 返回时填充此列表，表示实际生成的数据
     * @return 创建好的 HeapFile 对象
     * @throws IOException IO异常
     */
    public static HeapFile createRandomHeapFile(int columns, int rows, Map<Integer, Integer> columnSpecification,
                                                List<List<Integer>> tuples) throws IOException {
        return createRandomHeapFile(columns, rows, MAX_RAND_VALUE, columnSpecification, tuples);
    }

    /**
     * 带最大值限制的 HeapFile 创建方法
     */
    public static HeapFile createRandomHeapFile(int columns, int rows, int maxValue,
                                                Map<Integer, Integer> columnSpecification, List<List<Integer>> tuples)
                                                                                                                      throws IOException {
        File temp = createRandomHeapFileUnopened(columns, rows, maxValue, columnSpecification, tuples);
        return Utility.openHeapFile(columns, temp);
    }

    /**
     * 带列前缀名称的 HeapFile 创建方法
     */
    public static HeapFile createRandomHeapFile(int columns, int rows, Map<Integer, Integer> columnSpecification,
                                                List<List<Integer>> tuples, String colPrefix) throws IOException {
        return createRandomHeapFile(columns, rows, MAX_RAND_VALUE, columnSpecification, tuples, colPrefix);
    }

    /**
     * 带最大值限制和列名前缀的 HeapFile 创建方法
     */
    public static HeapFile createRandomHeapFile(int columns, int rows, int maxValue,
                                                Map<Integer, Integer> columnSpecification, List<List<Integer>> tuples,
                                                String colPrefix) throws IOException {
        File temp = createRandomHeapFileUnopened(columns, rows, maxValue, columnSpecification, tuples);
        return Utility.openHeapFile(columns, colPrefix, temp);
    }

    /**
     * 创建一个未打开的临时 HeapFile 文件（.dat 格式）
     *
     * @param columns 列数
     * @param rows 行数
     * @param maxValue 最大随机值
     * @param columnSpecification 列索引 -> 固定值 映射表
     * @param tuples 返回时填充的元组列表
     * @return 创建的临时文件对象
     * @throws IOException IO异常
     */
    public static File createRandomHeapFileUnopened(int columns, int rows, int maxValue,
                                                    Map<Integer, Integer> columnSpecification,
                                                    List<List<Integer>> tuples) throws IOException {
        if (tuples != null) {
            tuples.clear();
        } else {
            tuples = new ArrayList<>(rows);
        }

        Random r = new Random();

        // 生成所有元组数据
        for (int i = 0; i < rows; ++i) {
            List<Integer> tuple = new ArrayList<>(columns);
            for (int j = 0; j < columns; ++j) {
                Integer columnValue = null;
                if (columnSpecification != null)
                    columnValue = columnSpecification.get(j);
                if (columnValue == null) {
                    columnValue = r.nextInt(maxValue); // 随机生成
                }
                tuple.add(columnValue);
            }
            tuples.add(tuple);
        }

        // 创建临时文件并写入数据
        File temp = File.createTempFile("table", ".dat");
        temp.deleteOnExit(); // 测试后自动删除
        HeapFileEncoder.convert(tuples, temp, BufferPool.getPageSize(), columns);
        return temp;
    }

    /**
     * 将一个 Tuple 转换为 List<Integer>
     */
    public static List<Integer> tupleToList(Tuple tuple) {
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < tuple.getTupleDesc().numFields(); ++i) {
            int value = ((IntField) tuple.getField(i)).getValue();
            list.add(value);
        }
        return list;
    }

    /**
     * 验证某个 DbFile 的内容是否与预期的元组列表一致（使用默认事务）
     */
    public static void matchTuples(DbFile f, List<List<Integer>> tuples) throws DbException,
                                                                        TransactionAbortedException {
        TransactionId tid = new TransactionId();
        matchTuples(f, tid, tuples);
        Database.getBufferPool().transactionComplete(tid);
    }

    /**
     * 使用指定事务验证 DbFile 内容是否与预期一致
     */
    public static void matchTuples(DbFile f, TransactionId tid, List<List<Integer>> tuples) throws DbException,
                                                                                           TransactionAbortedException {
        SeqScan scan = new SeqScan(tid, f.getId(), "");
        matchTuples(scan, tuples);
    }

    /**
     * 验证 OpIterator 扫描出的内容是否与预期一致
     */
    public static void matchTuples(OpIterator iterator, List<List<Integer>> tuples) throws DbException,
                                                                                   TransactionAbortedException {
        List<List<Integer>> copy = new ArrayList<>(tuples);

        if (Debug.isEnabled()) {
            Debug.log("Expected tuples:");
            for (List<Integer> t : copy) {
                Debug.log("\t" + Utility.listToString(t));
            }
        }

        iterator.open();
        while (iterator.hasNext()) {
            Tuple t = iterator.next();
            List<Integer> list = tupleToList(t);
            boolean isExpected = copy.remove(list);
            Debug.log("scanned tuple: %s (%s)", t, isExpected ? "expected" : "not expected");
            if (!isExpected) {
                Assert.fail("expected tuples does not contain: " + t);
            }
        }
        iterator.close();

        if (!copy.isEmpty()) {
            StringBuilder msg = new StringBuilder("expected to find the following tuples:\n");
            final int MAX_TUPLES_OUTPUT = 10;
            int count = 0;
            for (List<Integer> t : copy) {
                if (count == MAX_TUPLES_OUTPUT) {
                    msg.append("[").append(copy.size() - MAX_TUPLES_OUTPUT).append(" more tuples]");
                    break;
                }
                msg.append("\t").append(Utility.listToString(t)).append("\n");
                count += 1;
            }
            Assert.fail(msg.toString());
        }
    }

    /**
     * 获取 JVM 当前使用的内存量（调用多次 GC 后）
     * @return 使用的内存大小（字节）
     */
    public static long getMemoryFootprint() {
        Runtime runtime = Runtime.getRuntime();
        long memAfter = runtime.totalMemory() - runtime.freeMemory();
        long memBefore = memAfter + 1;
        while (memBefore != memAfter) {
            memBefore = memAfter;
            System.gc();
            memAfter = runtime.totalMemory() - runtime.freeMemory();
        }
        return memAfter;
    }

    /**
     * 生成唯一标识字符串（每次调用返回不同 UUID）
     * @return 新的 UUID 字符串
     */
    public static String getUUID() {
        return UUID.randomUUID().toString();
    }

    /**
     * 计算数组相邻元素之间的差值序列
     * @param sequence 输入数组
     * @return 差值数组
     */
    private static double[] getDiff(double[] sequence) {
        double[] ret = new double[sequence.length - 1];
        for (int i = 0; i < sequence.length - 1; ++i)
            ret[i] = sequence[i + 1] - sequence[i];
        return ret;
    }

    /**
     * 检查输入序列是否近似为二次函数形式（如 an² + bn + c）
     * @param sequence 输入序列
     * @return Object[2]: [是否是二次序列(boolean), 二次项系数(double)]
     */
    public static Object[] checkQuadratic(double[] sequence) {
        Object[] ret = checkLinear(getDiff(sequence)); // 一阶差分后判断是否线性
        ret[1] = (Double) ret[1] / 2.0; // 二次项系数是线性差分斜率的一半
        return ret;
    }

    /**
     * 检查输入序列是否近似为线性关系（如 an + b）
     * @param sequence 输入序列
     * @return Object[2]: [是否是线性(boolean), 公差/斜率(double)]
     */
    public static Object[] checkLinear(double[] sequence) {
        return checkConstant(getDiff(sequence)); // 一阶差分后判断是否常数
    }

    /**
     * 检查输入序列是否近似为固定值序列（如 c, c, c...）
     * @param sequence 输入序列
     * @return Object[2]: [是否是固定序列(boolean), 平均值(double)]
     */
    public static Object[] checkConstant(double[] sequence) {
        Object[] ret = new Object[2];
        double sum = .0;
        for (double value : sequence)
            sum += value;
        double av = sum / sequence.length;

        double sqsum = 0;
        for (double v : sequence)
            sqsum += (v - av) * (v - av);
        double std = Math.sqrt(sqsum / sequence.length);

        ret[0] = std < 1.0 ? Boolean.TRUE : Boolean.FALSE;
        ret[1] = av;
        return ret;
    }
}
